import java.util.*;
import java.util.concurrent.*;

public class RequestEntryHandler implements Runnable{
	//Data members
	private ISocket _socket = null; //Used for sending responses
	private DB _db = null; //Instance of DB
	private Queue<Message> _requests = null; //Holds requests
	private boolean _flgStop = false; //Indication whether stop handling requests
	private int _sleepTime = 1000; //Sleep time between requests
	
	private synchronized boolean isStop() {return _flgStop;}
	public synchronized void stop() {_flgStop = true;}
	
	/**
	 * Constructor
	 * @param socket - communication socket
	 * @param db - instance of the DB
	 */
	public RequestEntryHandler(ISocket socket, DB db) {
		_socket = socket;
		_db = db;
		_requests = new ConcurrentLinkedQueue<Message>(); //Create a new requests queue
	}
	
	/**
	 * Clears peers with peer version equals to none
	 * @param list
	 */
	private void removeNonVersionPeer(List<Record> list) {
		for (Iterator<Record> itr = list.iterator(); itr.hasNext();) {
			if (itr.next().get_peerVersion().equals(Utils.zeroDate()))
				itr.remove();
		}
	}
	
	/**
	 * Sends entries according to a given date
	 * @param from - date parameter
	 * @param msg - request message
	 */
	private void sendOnlyDate(Date from, Message msg) {
		List<Record> list = _db.getRecords(from);
		
		removeNonVersionPeer(list); //Clear peers with no peer version 
		
		Collections.sort(list);
		
		for (int i = 0; i < list.size(); ++i) { //Send each entry
			String data = String.format("entry($sn=%d,$n=%d)<%s>", 
					i+1,list.size(), list.get(i).toString());
			Message newMsg = new Message(data.getBytes(), msg.getAddress(), msg.getPort(),
					SessionsManager.getNameBySource(new Source(_socket,msg.getAddress(),msg.getPort())));
			_socket.send(newMsg);
		}	
	}

	/**
	 * Send entries according to a given date and peer name message (0,PeerName)
	 * @param from - given date
	 * @param peer - peer name
	 * @param msg - request message
	 */
	private void sendStartToPeer(Date from, String peer, Message msg) {
		List<Record> list = _db.getRecords(from); //Get records from the DB
		removeNonVersionPeer(list); //Remove non version peers
		
		Collections.sort(list);
		
		//Delete the tail of the list
		int index = list.indexOf(new Record(peer));
		if (index == -1)
			return; //
		
		list = list.subList(0, index);
		
		//Send each entry
		for (int i = 0; i < list.size(); ++i) {
			String data = String.format("entry($sn=%d,$n=%d)<%s>", 
					i+1,list.size(), list.get(i).toString());
			Message newMsg = new Message(data.getBytes(), msg.getAddress(), msg.getPort(),
					SessionsManager.getNameBySource(new Source(_socket,msg.getAddress(),msg.getPort())));
			_socket.send(newMsg);
		}
	}
	
	/**
	 * Send entries according to a given date and peer name message (PeerName1-PeerName2)
	 * @param from - a given date
	 * @param fromPeer - start peer
	 * @param toPeer - end peer
	 * @param msg - request message
	 */
	private void sendFromPeerToPeer(Date from, String fromPeer, String toPeer, Message msg) {
		//Get peers from DB
		List<Record> list = _db.getRecords(from);
		removeNonVersionPeer(list);
		Collections.sort(list);
		
		//Cut list to the specified interval
		int index = list.indexOf(new Record(toPeer));
		if (index == -1)
			return; 
		
		list = list.subList(0, index);
		List<Record> untilPeer = new LinkedList<Record>(list);
		
		index = untilPeer.indexOf(new Record(fromPeer));
		if (index == -1)
			return;
		
		untilPeer = untilPeer.subList(0, index+1);
		
		list.removeAll(untilPeer);
		
		//Send each entry
		for (int i = 0; i < list.size(); ++i) {
			String data = String.format("entry($sn=%d,$n=%d)<%s>", 
					i+1,list.size(), list.get(i).toString());
			Message newMsg = new Message(data.getBytes(), msg.getAddress(), msg.getPort(),
					SessionsManager.getNameBySource(new Source(_socket,msg.getAddress(),msg.getPort())));
			_socket.send(newMsg);
		}
	}
	
	/**
	 * Send entries according to a given date and peer name message (PeerName, ALL)
	 * @param from - a given date
	 * @param fromPeer - start of the interval
	 * @param msg - request message
	 */
	private void sendFromPeerUntilALL(Date from, String fromPeer, Message msg) {
		//Get peers from the DB
		List<Record> list = _db.getRecords(from);
		removeNonVersionPeer(list);
		Collections.sort(list);
		
		//Cut peer list to the tail
		List<Record> untilPeer = new LinkedList<Record>(list);
		int index = untilPeer.indexOf(new Record(fromPeer));
		if (index == -1)
			return;
		
		untilPeer = untilPeer.subList(0, index+1); //exclusive
		
		list.removeAll(untilPeer);
		
		
		//Send each entry
		for (int i = 0; i < list.size(); ++i) {
			String data = String.format("entry($sn=%d,$n=%d)<%s>", 
					i+1,list.size(), list.get(i).toString());
			Message newMsg = new Message(data.getBytes(), msg.getAddress(), msg.getPort(),
					SessionsManager.getNameBySource(new Source(_socket,msg.getAddress(),msg.getPort())));
			_socket.send(newMsg);
		}
	}
	
	/**
	 * Scans the requests queue and handle them
	 */
	public void Scan() { 
		while (!isStop()) {
			
			if (_requests.isEmpty()) {
				try {Thread.sleep(_sleepTime);}
				catch(InterruptedException ex) {System.out.println(ex.getMessage());}
				continue;
			}
			
			Message msg = _requests.poll();
			String[] parsed = Utils.parseMessage(new String(msg.getData()));
			
			Date from = Utils.StringToDate(parsed[0]); //Get date
			
			//Send only date
			if (parsed.length == 1) {
				sendOnlyDate(from, msg);
				continue;
			}
			
			String prefix = parsed[1];
			String peer1 = parsed[2];
			
			if (prefix.equals("range")) { //Range keyword
				int index = peer1.indexOf('-');
				if (index != -1) {
				
					String peer2 = peer1.substring(index + 1, peer1.length());
					peer1 = peer1.substring(0, index);
					sendFromPeerToPeer(from, peer1, peer2, msg);
				
				} else if (peer1.equals("0")) //0 parameter
					sendStartToPeer(from, parsed[3], msg);
				else if (parsed[3].equals("ALL")) //All parameter
					sendFromPeerUntilALL(from, peer1, msg);	
				
			} else if (prefix.equals("list")) { //List keyword
				List<String> peers = new LinkedList<String>();
				for (int i = 2; i < parsed.length; ++i)
					peers.add(parsed[i]);
				
				List<Record> records = _db.getRecords(from, peers);
				removeNonVersionPeer(records);
				
				for (int i = 0; i < records.size(); ++i) { //Send each entry
					String data = String.format("entry($sn=%d,$n=%d)<%s>", 
							i+1,records.size(), records.get(i).toString());
					Message newMsg = new Message(data.getBytes(), msg.getAddress(), msg.getPort(),
							SessionsManager.getNameBySource(new Source(_socket,msg.getAddress(),msg.getPort())));
					_socket.send(newMsg);
				}
			}
		}
	}
	
	/**
	 * Add another request to queue
	 * @param msg - request message
	 */
	public void handle(Message msg) {_requests.add(msg);}
	
	public void run() {Scan();}
}
